<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h1>姓名是{{  name   }}</h1>
        <h2>年龄是{{age}}</h2>
        {{name}}
        <h3>姓名是{{  name   }};年龄是{{age}}</h3>
        <input type="text" v-model='name'>
        <input type="text" v-model='age'>
    </div>
</body>
</html>
<!-- <script src="../node_modules/vue/dist/vue.js"></script> -->
<script>
    function observe(data){
      if(typeof data !== 'object')return;
      // 用来劫持数据
      let keys = Object.keys(data);// keys 是所有属性名组成的数组
      keys.forEach(key=>{
        defineReactive(data,key,data[key])
      })

    }
    function defineReactive(obj,key,value){
      //专门调用defineProperty 实现数据数据劫持
      observe(value);
      let dep = new Dep;
      Object.defineProperty(obj,key,{
        get(){
          console.log('get')
          if(Dep.target123){
            //Dep.target  就是watcher实例
            dep.addSub(Dep.target123)
          }
          return value
        },
        set(newVal){
          console.log('set')
        
          if(value !== newVal){
            value = newVal;
            observe(value);
            dep.notify();
          }
          
        }
      })
    }

    function nodeToFragment(node,vm){
      // 把元素节点 转移到了 文档碎片上
      let child;
      let fragment = document.createDocumentFragment();
      
      while(child = node.firstChild){
        fragment.appendChild(child)
        compile(child,vm)
      }
      // while循环  只是 把node中的每一个子节点 都转移到了 fragment上
      // 转移完成之后  页面中的 node 节点 里边 就没有元素了
      node.appendChild(fragment)
      // 又把 fragment上的所有节点 一次性还给了 node
    }
    function compile(node,vm){
      // 判断node的节点类型 看他是不是元素节点
      
      if(node.nodeType == 1){
        //证明是元素节点  那么 我们要去处理行内属性
        let attrs = node.attributes;// 所有的行内属性,然后看那个是v-开头的
        [...attrs].forEach(item=>{
          if(/^v\-/.test(item.nodeName)){
            //证明这个属性是v-开头的
            let valName = item.nodeValue;// 获取 "name" 这个单词
            new Watcher(node,vm,valName)
            let val = vm.$data[valName];// 获取"name"对应的值:珠峰
            node.value = val;//把 珠峰 这两个字 放到input框中;
            node.addEventListener('input',(e)=>{
              //要把更改之后的input框的内容 设置给name
              vm.$data[valName] = e.target.value
            })
          }
        });
        [...node.childNodes].forEach(item=>{
          //针对有子节点的元素 接着进行编译
          compile(item,vm)
        })
      }else{
        // 文本节点
        // debugger
        let str = node.textContent; // "{{ name    }}{{  age  }}"
        node.str = str;
        // console.log(str)
        if(/\{\{(.+?)\}\}/.test(str)){
          str = str.replace(/\{\{(.+?)\}\}/g,(a,b)=>{
            // console.log(a,b)
            b = b.replace(/^ +| +$/g,'');// 去除首尾空格
            new Watcher(node,vm,b)
            return vm.$data[b]
          })
          // console.log(str)
          node.textContent = str
        }
      }
    }

    // 订阅器
    class Dep{
      constructor(){
        this.subs = [];
      }
      addSub(sub){
        this.subs.push(sub)
      }
      notify(){
        this.subs.forEach(item=>{
          // 让对应的事件 做更新操作
          item.update();
        })
      }
    }
    class Watcher{
      constructor(node,vm,key){
        Dep.target123  = this;
        this.node = node;
        this.vm = vm;
        this.key = key;
        this.get123();
        Dep.target123 = null;
      }
      update(){
        this.get123();
        if(this.node.nodeType==1){
          // 就是input
          this.node.value = this.value
        }else{
          let str = this.node.str;// 姓名是{{name}}
          str = str.replace(/\{\{(.+?)\}\}/g,(a,b)=>{
            b = b.trim();
            // if(b == this.key){
            //   return this.value
            // }else{
              return this.vm.$data[b]
            // }
          })
          this.node.textContent = str
        }
      }
      get123(){
        this.value = this.vm.$data[this.key]
      }
    }
    /* 
      每创造一个watcher实例 我们都要把这个实例放到对应属性的事件池中
      怎么实现?
      每当 new Watcher的时候  我们都把实例 放到 订阅器的一个静态属性上;
      然后 主动出发 该属性的 对应的get函数? 是通过 主动调用实例的get123方法
      get123 这个方法使用了 对应的属性 这是就会触发对应的get
      等对应的get执行完成之后呢? 再把这个静态属性清空? 因为使用这个属性的方式很多

      我们只单单要 再模板编译的时候(new Watcher)的时才需要向事件池中添加内容
     */


    function Vue(options){
      // $el  存储的是当前元素
      this.$el = document.querySelector(options.el)

      // $data存储的是data中的属性
      this.$data = options.data;
      observe(this.$data)// 数据劫持
      nodeToFragment(this.$el,this)// 模板编译
      // 我们使用观察者模式 把这两条线 联系起来
    }
    let vm = new Vue({
        el:'#app',
        data:{
            name:"珠峰666",
            age:100,
            c:{
              b:123
            }
        }
    });
</script>